Skip to content

Commit 57b457b

Browse files
committed
Accept a single rescan timestamp for all descriptors
Bitcoin Core scans since the earliest timestamp provided within the `importmulti` batch, so specifying different timestamps isn't really very useufl. This is still supported via the lower-level Rust API, but not exposed through the CLI/FF Interfaces, which are now simplified.
1 parent a93c524 commit 57b457b

10 files changed

Lines changed: 110 additions & 63 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ Breaking CLI changes:
3838

3939
- Renamed `--http-server-addr` to `--http-addr` and `--electrum-rpc-addr` to `--electrum-addr`
4040

41+
- The CLI now accepts a single `--rescan-since` timestamp instead of a separate one for each descriptor/xpub.
42+
4143
## 0.1.5 - 2020-10-05
4244

4345
- Reproducible builds using Docker (#51)

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,10 @@ You can set multiple `--xpub`s to track. This also supports ypubs and zpubs.
136136

137137
You can also track output script descriptors using `--descriptor`. For example, `--descriptor 'wpkh(<xpub>/0/*)'`.
138138

139-
Rescanning can be controlled with `--xpub <xpub>@<rescan>`. You can specify `<rescan>` with the wallet birthday formatted
140-
as `yyyy-mm-dd` to scan from that date onwards only, or use `now` to disable rescanning and watch for new transactions only (for newly created wallets).
141-
*Setting this can significantly speed up scanning and is highly recommended.*
139+
To speed up rescanning for historical transactions, you can provide the wallet creation date with `--rescan-since <timestmap>`.
140+
The timestamp can be a `YYYY-MM-DD` formatted string, or 'now' to disable rescanning and watch for new
141+
transactions only (for newly created wallets).
142+
*Setting this is highly recommended.*
142143

143144
By default, the Electrum server will be bound on port `50001`/`60001`/`60401` (according to the network)
144145
and the HTTP server will be bound on port `3060`. This can be controlled with `--electrum-addr`
@@ -166,7 +167,8 @@ For example:
166167
NETWORK=regtest
167168
GAP_LIMIT=20
168169
XPUBS='<xpub1>;<xpub2>'
169-
DESCRIPTORS='pkh(<xpub>/0/*)@2020-01-01'
170+
DESCRIPTORS='pkh(<xpub>/0/*)'
171+
RESCAN_SINCE=2020-01-01
170172
```
171173

172174
Setting the environment variables directly is also supported.
@@ -187,7 +189,7 @@ This removes several large dependencies and disables the `track-spends` database
187189

188190
You can use bwt with pruning, but:
189191

190-
1. You will have to provide a rescan date (via `--xpub <xpub>@<rescan>`) that is within the range of non-pruned blocks, or use `none` to disable rescanning entirely.
192+
1. You will have to provide a rescan date (via `--rescan-since`) that is within the range of non-pruned blocks, or use `none` to disable rescanning entirely.
191193

192194
2. Electrum needs to be run with `--skipmerklecheck` to tolerate missing SPV proofs for transactions in pruned blocks.
193195

contrib/electrum-plugin/bwt.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ def start(self):
5858
'--network', get_network_name(),
5959
'--bitcoind-url', self.bitcoind_url,
6060
'--bitcoind-dir', self.bitcoind_dir,
61+
'--rescan-since', self.rescan_since,
6162
'--electrum-addr', '127.0.0.1:%d' % self.rpc_port,
6263
'--electrum-skip-merkle',
6364
'--no-startup-banner',
@@ -74,7 +75,7 @@ def start(self):
7475

7576
for wallet in self.wallets:
7677
for xpub in wallet.get_master_public_keys():
77-
args.extend([ '--xpub', '%s@%s' % (xpub, self.rescan_since) ])
78+
args.extend([ '--xpub', xpub ])
7879

7980
for i in range(self.verbose):
8081
args.append('-v')

contrib/nodejs-bwt-daemon/README.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ location, on the default ports and with cookie auth enabled, this should Just Wo
3232
```js
3333
import BwtDaemon from 'bwt-daemon'
3434

35-
let bwtd = await BwtDaemon({
36-
xpubs: [ [ 'xpub66...', 'now' ] ],
35+
const bwtd = await BwtDaemon({
36+
xpubs: [ 'xpub66...' ],
3737
electrum: true,
3838
})
3939

@@ -43,17 +43,19 @@ console.log('bwt electrum server ready on', bwtd.electrum_addr)
4343
With some more advanced options:
4444

4545
```js
46-
let bwtd = await BwtDaemon({
46+
const bwtd = await BwtDaemon({
4747
// Network and Bitcoin Core RPC settings
4848
network: 'regtest',
4949
bitcoind_dir: '/home/satoshi/.bitcoin',
5050
bitcoind_url: 'http://127.0.0.1:9008/',
5151
bitcoind_wallet: 'bwt',
5252

53-
// Descriptors or xpubs to track as an array of (desc_or_xpub, rescan_since) tuples
54-
// Use 'now' to look for new transactions only, or the unix timestamp to begin rescanning from.
55-
descriptors: [ [ 'wpkh(tpub61.../0/*)', 'now' ] ],
56-
xpubs: [ [ 'tpub66...', 'now' ] ],
53+
// Descriptors and xpubs to track
54+
descriptors: [ 'wpkh(tpub61.../0/*)' ],
55+
xpubs: [ 'tpub66...' ],
56+
57+
// Rescan since timestamp. Accepts unix timestamps, date strings, Date objects, or 'now' to look for new transactions only
58+
rescan_since: '2020-01-01',
5759

5860
// Enable HTTP and Electrum servers
5961
http: true,

contrib/nodejs-bwt-daemon/example.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const BwtDaemon = require('bwt-daemon')
77
network: 'regtest',
88
bitcoind_dir: '/tmp/bd1',
99
bitcoind_wallet: 'bwt',
10-
descriptors: [ [ my_desc, 'now' ] ],
10+
descriptors: [ my_desc ],
1111
electrum: true,
1212
http: true,
1313
verbose: 2,

contrib/nodejs-bwt-daemon/index.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ function init(options) {
4040
delete options.progress
4141
}
4242

43+
if (options.rescan_since) {
44+
options.rescan_since = parse_timestamp(options.rescan_since)
45+
}
46+
4347
// Convenience shortcuts
4448
if (options.electrum) {
4549
options.electrum_addr || (options.electrum_addr = '127.0.0.1:0')
@@ -97,3 +101,19 @@ class BwtDaemon {
97101
}
98102

99103
module.exports = init.BwtDaemon = init
104+
105+
// Utility
106+
107+
function parse_timestamp(ts) {
108+
// Pass 'now' as is
109+
if (ts == 'now') return ts
110+
// Date objects
111+
if (ts.getTime) return ts.getTime()/1000|0
112+
// Unix timestamp
113+
if (!isNaN(ts)) return +ts
114+
// Date string (e.g. YYYY-MM-DD)
115+
const dt = new Date(ts)
116+
if (!isNaN(dt.getTime())) return dt.getTime()/1000|0
117+
118+
throw new Error(`Invalid rescan since value: ${ts}`)
119+
}

examples/use-from-rust.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ fn main() -> Result<()> {
1010
bitcoind_dir: Some("/home/satoshi/.bitcoin".into()),
1111
bitcoind_wallet: Some("bwt".into()),
1212
electrum_addr: Some("127.0.0.1:0".parse().unwrap()),
13-
descriptors: vec![(my_desc.parse().unwrap(), RescanSince::Timestamp(0))],
13+
descriptors: vec![my_desc.parse().unwrap()],
14+
rescan_since: RescanSince::Now,
1415
verbose: 2,
1516
..Default::default()
1617
};

src/app.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ impl App {
4040
&config.descriptors[..],
4141
&config.xpubs[..],
4242
&config.bare_xpubs[..],
43+
config.rescan_since,
4344
config.network,
4445
config.gap_limit,
4546
config.initial_import_size,

src/config.rs

Lines changed: 56 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -127,37 +127,61 @@ pub struct Config {
127127
#[cfg_attr(feature = "cli", structopt(
128128
short = "d",
129129
long = "descriptor",
130-
help = "Descriptors to track (scans for history from the genesis by default, use <desc>@<yyyy-mm-dd> or <desc>@<unix-epoch> to specify a rescan timestmap, or <desc>@none to disable rescan)",
130+
help = "Add a descriptor to track",
131131
parse(try_from_str = parse_desc),
132132
env, hide_env_values(true),
133133
use_delimiter(true), value_delimiter(";"),
134134
display_order(20)
135135
))]
136136
#[serde(default)]
137-
pub descriptors: Vec<(ExtendedDescriptor, RescanSince)>,
137+
pub descriptors: Vec<ExtendedDescriptor>,
138138

139-
#[cfg_attr(feature = "cli", structopt(
140-
short = "x",
141-
long = "xpub",
142-
help = "xpubs to track (represented as two separate descriptors for the internal/external chains, supports <xpub>@<rescan-time>)",
143-
parse(try_from_str = parse_xpub),
144-
env, hide_env_values(true),
145-
use_delimiter(true), value_delimiter(";"),
146-
display_order(21)
147-
))]
139+
#[cfg_attr(
140+
feature = "cli",
141+
structopt(
142+
short = "x",
143+
long = "xpub",
144+
help = "Add an extended public key to track (with separate internal/external chains)",
145+
env,
146+
hide_env_values(true),
147+
use_delimiter(true),
148+
value_delimiter(";"),
149+
display_order(21)
150+
)
151+
)]
148152
#[serde(default)]
149-
pub xpubs: Vec<(XyzPubKey, RescanSince)>,
153+
pub xpubs: Vec<XyzPubKey>,
150154

151-
#[cfg_attr(feature = "cli", structopt(
152-
short = "X",
153-
long = "bare-xpub",
154-
help = "Bare xpubs to track (like --xpub, but does not derive separate internal/external chains)",
155-
parse(try_from_str = parse_xpub),
156-
env, hide_env_values(true), use_delimiter(true),
157-
display_order(22)
158-
))]
155+
#[cfg_attr(
156+
feature = "cli",
157+
structopt(
158+
short = "X",
159+
long = "bare-xpub",
160+
help = "Add a bare extended public key to track (without separate internal/external chains)",
161+
env,
162+
hide_env_values(true),
163+
use_delimiter(true),
164+
display_order(22)
165+
)
166+
)]
159167
#[serde(default)]
160-
pub bare_xpubs: Vec<(XyzPubKey, RescanSince)>,
168+
pub bare_xpubs: Vec<XyzPubKey>,
169+
170+
#[cfg_attr(
171+
feature = "cli",
172+
structopt(
173+
short = "R",
174+
long,
175+
help = "Start date for wallet history rescan. Accepts YYYY-MM-DD formatted strings, unix timestamps, or 'now' to watch for new transactions only",
176+
parse(try_from_str = parse_rescan),
177+
default_value = "0",
178+
env,
179+
hide_env_values(true),
180+
display_order(29)
181+
)
182+
)]
183+
#[serde(default = "default_rescan_since")]
184+
pub rescan_since: RescanSince,
161185

162186
#[cfg_attr(
163187
feature = "cli",
@@ -452,34 +476,23 @@ fn apply_log_env(mut builder: LogBuilder) -> LogBuilder {
452476
}
453477

454478
#[cfg(feature = "cli")]
455-
fn parse_desc(s: &str) -> Result<(ExtendedDescriptor, RescanSince)> {
479+
fn parse_desc(s: &str) -> Result<ExtendedDescriptor> {
456480
use crate::util::descriptor::DescriptorChecksum;
457-
let mut parts = s.trim().splitn(2, '@');
458-
let desc = ExtendedDescriptor::parse_with_checksum(parts.next().req()?)?;
459-
let rescan = parse_rescan(parts.next())?;
460-
Ok((desc, rescan))
481+
Ok(ExtendedDescriptor::parse_with_checksum(s)?)
461482
}
462483

463484
#[cfg(feature = "cli")]
464-
fn parse_xpub(s: &str) -> Result<(XyzPubKey, RescanSince)> {
465-
let mut parts = s.trim().splitn(2, '@');
466-
let xpub = parts.next().req()?.parse()?;
467-
let rescan = parse_rescan(parts.next())?;
468-
Ok((xpub, rescan))
469-
}
470-
471-
#[cfg(feature = "cli")]
472-
fn parse_rescan(s: Option<&str>) -> Result<RescanSince> {
485+
fn parse_rescan(s: &str) -> Result<RescanSince> {
473486
use crate::error::Context;
474487
Ok(match s {
475-
None | Some("all") => RescanSince::Timestamp(0),
476-
Some("now") | Some("none") => RescanSince::Now,
477-
Some(s) => {
488+
"all" | "genesis" => RescanSince::Timestamp(0),
489+
"now" | "none" => RescanSince::Now,
490+
s => {
478491
// try as a unix timestamp first, then as a datetime string
479492
RescanSince::Timestamp(
480493
s.parse::<u64>()
481494
.or_else(|_| parse_yyyymmdd(s))
482-
.context("invalid rescan value")?,
495+
.context("invalid rescan-since value")?,
483496
)
484497
}
485498
})
@@ -566,6 +579,7 @@ defaultable!(Config,
566579
)
567580
@custom(
568581
network=Network::Bitcoin,
582+
rescan_since=RescanSince::Timestamp(0),
569583
gap_limit=20,
570584
initial_import_size=350,
571585
poll_interval=time::Duration::from_secs(5),
@@ -577,6 +591,9 @@ defaultable!(Config,
577591
fn default_network() -> Network {
578592
Network::Bitcoin
579593
}
594+
fn default_rescan_since() -> RescanSince {
595+
RescanSince::Timestamp(0)
596+
}
580597
fn default_gap_limit() -> u32 {
581598
20
582599
}

src/wallet.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,46 +35,47 @@ impl WalletWatcher {
3535
}
3636

3737
pub fn from_config(
38-
descs: &[(ExtendedDescriptor, RescanSince)],
39-
xpubs: &[(XyzPubKey, RescanSince)],
40-
bare_xpubs: &[(XyzPubKey, RescanSince)],
38+
descs: &[ExtendedDescriptor],
39+
xpubs: &[XyzPubKey],
40+
bare_xpubs: &[XyzPubKey],
41+
rescan_since: RescanSince,
4142
network: Network,
4243
gap_limit: u32,
4344
initial_import_size: u32,
4445
) -> Result<Self> {
4546
let mut wallets = vec![];
46-
for (desc, rescan) in descs {
47+
for desc in descs {
4748
wallets.push(
4849
Wallet::from_descriptor(
4950
desc.clone(),
5051
network,
5152
gap_limit,
5253
initial_import_size,
53-
*rescan,
54+
rescan_since,
5455
)
5556
.with_context(|| format!("invalid descriptor {}", desc))?,
5657
);
5758
}
58-
for (xpub, rescan) in xpubs {
59+
for xpub in xpubs {
5960
wallets.append(
6061
&mut Wallet::from_xpub(
6162
xpub.clone(),
6263
network,
6364
gap_limit,
6465
initial_import_size,
65-
*rescan,
66+
rescan_since,
6667
)
6768
.with_context(|| format!("invalid xpub {}", xpub))?,
6869
);
6970
}
70-
for (xpub, rescan) in bare_xpubs {
71+
for xpub in bare_xpubs {
7172
wallets.push(
7273
Wallet::from_bare_xpub(
7374
xpub.clone(),
7475
network,
7576
gap_limit,
7677
initial_import_size,
77-
*rescan,
78+
rescan_since,
7879
)
7980
.with_context(|| format!("invalid xpub {}", xpub))?,
8081
);

0 commit comments

Comments
 (0)