Skip to content

Commit f0c876e

Browse files
committed
Fix wallet sync not finding coins of addresses which are not cached
Previously, electrum-based blockchain implementations only synced for `scriptPubKey`s that are already cached in `Database`. This PR introduces a feedback mechanism, that uses `stop_gap` and the difference between "current index" and "last active index" to determine whether we need to cache more `scriptPubKeys`. The `WalletSync::wallet_setup` trait now returns a `usize` on success. This represents the number of extra `scriptPubKey`s to cache, in order to satisfy `stop_gap` for the next call. `Wallet::sync` now calls `WalletSync` in a loop, cacheing inbetween subsequent calls (if needed).
1 parent 9165fae commit f0c876e

File tree

10 files changed

+210
-64
lines changed

10 files changed

+210
-64
lines changed

src/blockchain/any.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ impl WalletSync for AnyBlockchain {
133133
&self,
134134
database: &mut D,
135135
progress_update: Box<dyn Progress>,
136-
) -> Result<(), Error> {
136+
) -> Result<usize, Error> {
137137
maybe_await!(impl_inner_method!(
138138
self,
139139
wallet_sync,
@@ -146,7 +146,7 @@ impl WalletSync for AnyBlockchain {
146146
&self,
147147
database: &mut D,
148148
progress_update: Box<dyn Progress>,
149-
) -> Result<(), Error> {
149+
) -> Result<usize, Error> {
150150
maybe_await!(impl_inner_method!(
151151
self,
152152
wallet_setup,

src/blockchain/compact_filters/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ impl WalletSync for CompactFiltersBlockchain {
276276
&self,
277277
database: &mut D,
278278
progress_update: Box<dyn Progress>,
279-
) -> Result<(), Error> {
279+
) -> Result<usize, Error> {
280280
let first_peer = &self.peers[0];
281281

282282
let skip_blocks = self.skip_blocks.unwrap_or(0);
@@ -474,7 +474,7 @@ impl WalletSync for CompactFiltersBlockchain {
474474
.unwrap()
475475
.update(100.0, Some("Done".into()))?;
476476

477-
Ok(())
477+
Ok(0)
478478
}
479479
}
480480

src/blockchain/electrum.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ impl WalletSync for ElectrumBlockchain {
110110
&self,
111111
database: &mut D,
112112
_progress_update: Box<dyn Progress>,
113-
) -> Result<(), Error> {
113+
) -> Result<usize, Error> {
114114
let mut request = script_sync::start(database, self.stop_gap)?;
115115
let mut block_times = HashMap::<u32, u32>::new();
116116
let mut txid_to_height = HashMap::<Txid, u32>::new();
@@ -124,7 +124,9 @@ impl WalletSync for ElectrumBlockchain {
124124
// tranascations than in the request. This should never happen but we don't want to panic.
125125
let electrum_goof = || Error::Generic("electrum server misbehaving".to_string());
126126

127-
let batch_update = loop {
127+
// `missing_count` is the min number of scripts to cache, so that we may try to satisfy
128+
// `stop_gap` with the next attempted sync
129+
let (batch_update, missing_count) = loop {
128130
request = match request {
129131
Request::Script(script_req) => {
130132
let scripts = script_req.request().take(chunk_size);
@@ -232,12 +234,14 @@ impl WalletSync for ElectrumBlockchain {
232234

233235
tx_req.satisfy(full_details)?
234236
}
235-
Request::Finish(batch_update) => break batch_update,
237+
Request::Finish(batch_update, missing_count) => {
238+
break (batch_update, missing_count)
239+
}
236240
}
237241
};
238242

239243
database.commit_batch(batch_update)?;
240-
Ok(())
244+
Ok(missing_count)
241245
}
242246
}
243247

src/blockchain/esplora/reqwest.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,12 @@ impl WalletSync for EsploraBlockchain {
131131
&self,
132132
database: &mut D,
133133
_progress_update: Box<dyn Progress>,
134-
) -> Result<(), Error> {
134+
) -> Result<usize, Error> {
135135
use crate::blockchain::script_sync::Request;
136136
let mut request = script_sync::start(database, self.stop_gap)?;
137137
let mut tx_index: HashMap<Txid, Tx> = HashMap::new();
138138

139-
let batch_update = loop {
139+
let (batch_update, missing_count) = loop {
140140
request = match request {
141141
Request::Script(script_req) => {
142142
let futures: FuturesOrdered<_> = script_req
@@ -208,13 +208,14 @@ impl WalletSync for EsploraBlockchain {
208208
.collect::<Result<_, Error>>()?;
209209
tx_req.satisfy(full_txs)?
210210
}
211-
Request::Finish(batch_update) => break batch_update,
211+
Request::Finish(batch_update, missing_count) => {
212+
break (batch_update, missing_count)
213+
}
212214
}
213215
};
214216

215217
database.commit_batch(batch_update)?;
216-
217-
Ok(())
218+
Ok(missing_count)
218219
}
219220
}
220221

src/blockchain/esplora/ureq.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,11 @@ impl WalletSync for EsploraBlockchain {
124124
&self,
125125
database: &mut D,
126126
_progress_update: Box<dyn Progress>,
127-
) -> Result<(), Error> {
127+
) -> Result<usize, Error> {
128128
use crate::blockchain::script_sync::Request;
129129
let mut request = script_sync::start(database, self.stop_gap)?;
130130
let mut tx_index: HashMap<Txid, Tx> = HashMap::new();
131-
let batch_update = loop {
131+
let (batch_update, missing_count) = loop {
132132
request = match request {
133133
Request::Script(script_req) => {
134134
let scripts = script_req
@@ -206,13 +206,15 @@ impl WalletSync for EsploraBlockchain {
206206
.collect::<Result<_, Error>>()?;
207207
tx_req.satisfy(full_txs)?
208208
}
209-
Request::Finish(batch_update) => break batch_update,
209+
Request::Finish(batch_update, missing_count) => {
210+
break (batch_update, missing_count)
211+
}
210212
}
211213
};
212214

213215
database.commit_batch(batch_update)?;
214216

215-
Ok(())
217+
Ok(missing_count)
216218
}
217219
}
218220

src/blockchain/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ pub trait WalletSync {
135135
&self,
136136
database: &mut D,
137137
progress_update: Box<dyn Progress>,
138-
) -> Result<(), Error>;
138+
) -> Result<usize, Error>;
139139

140140
/// If not overridden, it defaults to calling [`Self::wallet_setup`] internally.
141141
///
@@ -158,7 +158,7 @@ pub trait WalletSync {
158158
&self,
159159
database: &mut D,
160160
progress_update: Box<dyn Progress>,
161-
) -> Result<(), Error> {
161+
) -> Result<usize, Error> {
162162
maybe_await!(self.wallet_setup(database, progress_update))
163163
}
164164
}
@@ -379,15 +379,15 @@ impl<T: WalletSync> WalletSync for Arc<T> {
379379
&self,
380380
database: &mut D,
381381
progress_update: Box<dyn Progress>,
382-
) -> Result<(), Error> {
382+
) -> Result<usize, Error> {
383383
maybe_await!(self.deref().wallet_setup(database, progress_update))
384384
}
385385

386386
fn wallet_sync<D: BatchDatabase>(
387387
&self,
388388
database: &mut D,
389389
progress_update: Box<dyn Progress>,
390-
) -> Result<(), Error> {
390+
) -> Result<usize, Error> {
391391
maybe_await!(self.deref().wallet_sync(database, progress_update))
392392
}
393393
}

src/blockchain/rpc.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ impl WalletSync for RpcBlockchain {
180180
&self,
181181
database: &mut D,
182182
progress_update: Box<dyn Progress>,
183-
) -> Result<(), Error> {
183+
) -> Result<usize, Error> {
184184
let mut scripts_pubkeys = database.iter_script_pubkeys(Some(KeychainKind::External))?;
185185
scripts_pubkeys.extend(database.iter_script_pubkeys(Some(KeychainKind::Internal))?);
186186
debug!(
@@ -262,7 +262,7 @@ impl WalletSync for RpcBlockchain {
262262
&self,
263263
db: &mut D,
264264
_progress_update: Box<dyn Progress>,
265-
) -> Result<(), Error> {
265+
) -> Result<usize, Error> {
266266
let mut indexes = HashMap::new();
267267
for keykind in &[KeychainKind::External, KeychainKind::Internal] {
268268
indexes.insert(*keykind, db.get_last_index(*keykind)?.unwrap_or(0));
@@ -395,7 +395,7 @@ impl WalletSync for RpcBlockchain {
395395
db.set_last_index(keykind, index)?;
396396
}
397397

398-
Ok(())
398+
Ok(0)
399399
}
400400
}
401401

src/blockchain/script_sync.rs

Lines changed: 65 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ pub enum Request<'a, D: BatchDatabase> {
2121
/// A request for full transaction details of some transactions.
2222
Tx(TxReq<'a, D>),
2323
/// Requests are finished here's a batch database update to reflect data gathered.
24-
Finish(D::Batch),
24+
/// we also return a value which represents the minimum number of scripts that we should cache
25+
/// for next round (or the missing cache count)
26+
Finish(D::Batch, usize),
2527
}
2628

2729
/// starts a sync
@@ -34,7 +36,8 @@ pub fn start<D: BatchDatabase>(db: &D, stop_gap: usize) -> Result<Request<'_, D>
3436
let scripts_needed = db
3537
.iter_script_pubkeys(Some(keychain))?
3638
.into_iter()
37-
.collect();
39+
.collect::<VecDeque<_>>();
40+
println!("scripts_needed count: {}", scripts_needed.len());
3841
let state = State::new(db);
3942

4043
Ok(Request::Script(ScriptReq {
@@ -117,39 +120,57 @@ impl<'a, D: BatchDatabase> ScriptReq<'a, D> {
117120
self.scripts_needed.pop_front();
118121
}
119122

120-
let last_active_index = self
123+
// last active index
124+
// 0 => No last active
125+
let last = self
121126
.state
122127
.last_active_index
123128
.get(&self.keychain)
124-
.map(|x| x + 1)
125-
.unwrap_or(0); // so no addresses active maps to 0
129+
.map(|&l| l + 1)
130+
.unwrap_or(0);
126131

127-
Ok(
128-
if self.script_index > last_active_index + self.stop_gap
129-
|| self.scripts_needed.is_empty()
130-
{
132+
// difference between current index and last active index
133+
let diff = self.script_index - last;
134+
135+
if diff <= self.stop_gap {
136+
if !self.scripts_needed.is_empty() {
137+
// we have not finished requesting txs with script batches, so continue
138+
return Ok(Request::Script(self));
139+
}
140+
141+
// if we obtained an active index in this sync, we have missing scripts in db cache,
142+
// report it
143+
if last > 0 {
131144
debug!(
132-
"finished scanning for transactions for keychain {:?} at index {}",
133-
self.keychain, last_active_index
145+
"reporting missing with: current={}, last={}, diff={}, gap={}",
146+
self.script_index, last, diff, self.stop_gap
134147
);
135-
// we're done here -- check if we need to do the next keychain
136-
if let Some(keychain) = self.next_keychains.pop() {
137-
self.keychain = keychain;
138-
self.script_index = 0;
139-
self.scripts_needed = self
140-
.state
141-
.db
142-
.iter_script_pubkeys(Some(keychain))?
143-
.into_iter()
144-
.collect();
145-
Request::Script(self)
146-
} else {
147-
Request::Tx(TxReq { state: self.state })
148-
}
149-
} else {
150-
Request::Script(self)
151-
},
152-
)
148+
self.state
149+
.missing_script_counts
150+
.insert(self.keychain, self.stop_gap - diff);
151+
}
152+
}
153+
154+
debug!(
155+
"finished scanning for txs of keychain {:?} at index {:?}",
156+
self.keychain, last
157+
);
158+
159+
if let Some(keychain) = self.next_keychains.pop() {
160+
// we still have another keychain to request txs with
161+
self.keychain = keychain;
162+
self.script_index = 0;
163+
self.scripts_needed = self
164+
.state
165+
.db
166+
.iter_script_pubkeys(Some(keychain))?
167+
.into_iter()
168+
.collect();
169+
return Ok(Request::Script(self));
170+
}
171+
172+
// We have finished requesting txids, let's get the actual txs.
173+
Ok(Request::Tx(TxReq { state: self.state }))
153174
}
154175
}
155176

@@ -276,7 +297,18 @@ impl<'a, D: BatchDatabase> ConftimeReq<'a, D> {
276297
}
277298

278299
if self.state.tx_missing_conftime.is_empty() {
279-
Ok(Request::Finish(self.state.into_db_update()?))
300+
// Obtain largest missing count (between external/internal) for simplicity
301+
// The reasoning is that there is no point returning a map - in the future, a single
302+
// database will only handle a single descriptor
303+
let missing = self
304+
.state
305+
.missing_script_counts
306+
.clone()
307+
.into_values()
308+
.reduce(std::cmp::max)
309+
.unwrap_or(0);
310+
311+
Ok(Request::Finish(self.state.into_db_update()?, missing))
280312
} else {
281313
Ok(Request::Conftime(self))
282314
}
@@ -294,6 +326,8 @@ struct State<'a, D> {
294326
tx_missing_conftime: BTreeMap<Txid, TransactionDetails>,
295327
/// The start of the sync
296328
start_time: Instant,
329+
/// Missing number of scripts to cache per keychain
330+
missing_script_counts: HashMap<KeychainKind, usize>,
297331
}
298332

299333
impl<'a, D: BatchDatabase> State<'a, D> {
@@ -305,6 +339,7 @@ impl<'a, D: BatchDatabase> State<'a, D> {
305339
tx_needed: BTreeSet::default(),
306340
tx_missing_conftime: BTreeMap::default(),
307341
start_time: Instant::new(),
342+
missing_script_counts: HashMap::default(),
308343
}
309344
}
310345
fn into_db_update(self) -> Result<D::Batch, Error> {

0 commit comments

Comments
 (0)