Skip to content

Commit 6bb313e

Browse files
committed
Merge branch 'fix/80' into v0.12
2 parents 801ad98 + affaa20 commit 6bb313e

File tree

7 files changed

+81
-69
lines changed

7 files changed

+81
-69
lines changed

src/cli/args.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ use crate::{AnyIndexer, Wallet};
4141
#[derive(Clone, Eq, PartialEq, Debug)]
4242
#[command(author, version, about)]
4343
pub struct Args<C: Clone + Eq + Debug + Subcommand, O: DescriptorOpts = DescrStdOpts> {
44-
/// Set verbosity level.
44+
/// Set verbosity level
4545
///
4646
/// Can be used multiple times to increase verbosity.
4747
#[clap(short, long, global = true, action = clap::ArgAction::Count)]
@@ -53,10 +53,14 @@ pub struct Args<C: Clone + Eq + Debug + Subcommand, O: DescriptorOpts = DescrStd
5353
#[command(flatten)]
5454
pub resolver: ResolverOpt,
5555

56-
/// Force-sync wallet data with the indexer before performing the operation.
56+
/// Force-sync wallet data with the indexer before performing the operation
5757
#[clap(long, global = true)]
5858
pub sync: bool,
5959

60+
/// Prune old transactions from the cache during the sync operation
61+
#[clap(long, global = true)]
62+
pub prune: bool,
63+
6064
#[command(flatten)]
6165
pub general: GeneralOpts,
6266

@@ -72,6 +76,7 @@ impl<C: Clone + Eq + Debug + Subcommand, O: DescriptorOpts> Args<C, O> {
7276
wallet: self.wallet.clone(),
7377
resolver: self.resolver.clone(),
7478
sync: self.sync,
79+
prune: self.prune,
7580
general: self.general.clone(),
7681
command: cmd.clone(),
7782
}
@@ -154,7 +159,7 @@ impl<C: Clone + Eq + Debug + Subcommand, O: DescriptorOpts> Args<C, O> {
154159
if sync {
155160
let indexer = self.indexer()?;
156161
eprint!("Syncing");
157-
if let Some(errors) = wallet.update(&indexer).into_err() {
162+
if let Some(errors) = wallet.update(&indexer, self.prune).into_err() {
158163
eprintln!(" partial, some requests has failed:");
159164
for err in errors {
160165
eprintln!("- {err}");

src/cli/command.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ impl<O: DescriptorOpts> Exec for Args<Command, O> {
323323
if *publish {
324324
let indexer = self.indexer()?;
325325
eprint!("Publishing transaction via {} ... ", indexer.name());
326-
indexer.publish(&tx)?;
326+
indexer.broadcast(&tx)?;
327327
eprintln!("success");
328328
}
329329
}
@@ -343,7 +343,7 @@ impl<O: DescriptorOpts> Exec for Args<Command, O> {
343343
if *publish {
344344
let indexer = self.indexer()?;
345345
eprint!("Publishing transaction via {} ... ", indexer.name());
346-
indexer.publish(&tx)?;
346+
indexer.broadcast(&tx)?;
347347
eprintln!("success");
348348
}
349349
}

src/indexers/any.rs

Lines changed: 8 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -72,63 +72,32 @@ pub enum AnyIndexerError {
7272
impl Indexer for AnyIndexer {
7373
type Error = AnyIndexerError;
7474

75-
fn create<K, D: Descriptor<K>, L2: Layer2>(
76-
&self,
77-
descr: &WalletDescr<K, D, L2::Descr>,
78-
) -> MayError<WalletCache<L2::Cache>, Vec<Self::Error>> {
79-
match self {
80-
#[cfg(feature = "electrum")]
81-
AnyIndexer::Electrum(inner) => {
82-
let result = inner.create::<K, D, L2>(descr);
83-
MayError {
84-
ok: result.ok,
85-
err: result.err.map(|v| v.into_iter().map(|e| e.into()).collect()),
86-
}
87-
}
88-
#[cfg(feature = "esplora")]
89-
AnyIndexer::Esplora(inner) => {
90-
let result = inner.create::<K, D, L2>(descr);
91-
MayError {
92-
ok: result.ok,
93-
err: result.err.map(|v| v.into_iter().map(|e| e.into()).collect()),
94-
}
95-
}
96-
#[cfg(feature = "mempool")]
97-
AnyIndexer::Mempool(inner) => {
98-
let result = inner.create::<K, D, L2>(descr);
99-
MayError {
100-
ok: result.ok,
101-
err: result.err.map(|v| v.into_iter().map(|e| e.into()).collect()),
102-
}
103-
}
104-
}
105-
}
106-
10775
fn update<K, D: Descriptor<K>, L2: Layer2>(
10876
&self,
10977
descr: &WalletDescr<K, D, L2::Descr>,
11078
cache: &mut WalletCache<L2::Cache>,
79+
prune: bool,
11180
) -> MayError<usize, Vec<Self::Error>> {
11281
match self {
11382
#[cfg(feature = "electrum")]
11483
AnyIndexer::Electrum(inner) => {
115-
let result = inner.update::<K, D, L2>(descr, cache);
84+
let result = inner.update::<K, D, L2>(descr, cache, prune);
11685
MayError {
11786
ok: result.ok,
11887
err: result.err.map(|v| v.into_iter().map(|e| e.into()).collect()),
11988
}
12089
}
12190
#[cfg(feature = "esplora")]
12291
AnyIndexer::Esplora(inner) => {
123-
let result = inner.update::<K, D, L2>(descr, cache);
92+
let result = inner.update::<K, D, L2>(descr, cache, prune);
12493
MayError {
12594
ok: result.ok,
12695
err: result.err.map(|v| v.into_iter().map(|e| e.into()).collect()),
12796
}
12897
}
12998
#[cfg(feature = "mempool")]
13099
AnyIndexer::Mempool(inner) => {
131-
let result = inner.update::<K, D, L2>(descr, cache);
100+
let result = inner.update::<K, D, L2>(descr, cache, prune);
132101
MayError {
133102
ok: result.ok,
134103
err: result.err.map(|v| v.into_iter().map(|e| e.into()).collect()),
@@ -137,14 +106,14 @@ impl Indexer for AnyIndexer {
137106
}
138107
}
139108

140-
fn publish(&self, tx: &Tx) -> Result<(), Self::Error> {
109+
fn broadcast(&self, tx: &Tx) -> Result<(), Self::Error> {
141110
match self {
142111
#[cfg(feature = "electrum")]
143-
AnyIndexer::Electrum(inner) => inner.publish(tx).map_err(|e| e.into()),
112+
AnyIndexer::Electrum(inner) => inner.broadcast(tx).map_err(|e| e.into()),
144113
#[cfg(feature = "esplora")]
145-
AnyIndexer::Esplora(inner) => inner.publish(tx).map_err(|e| e.into()),
114+
AnyIndexer::Esplora(inner) => inner.broadcast(tx).map_err(|e| e.into()),
146115
#[cfg(feature = "mempool")]
147-
AnyIndexer::Mempool(inner) => inner.publish(tx).map_err(|e| e.into()),
116+
AnyIndexer::Mempool(inner) => inner.broadcast(tx).map_err(|e| e.into()),
148117
}
149118
}
150119
}

src/indexers/electrum.rs

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
// limitations under the License.
2121

2222
use std::collections::BTreeMap;
23+
use std::mem;
2324
use std::num::NonZeroU32;
2425
use std::str::FromStr;
2526

@@ -63,21 +64,21 @@ pub enum ElectrumError {
6364
impl Indexer for Client {
6465
type Error = ElectrumError;
6566

66-
fn create<K, D: Descriptor<K>, L2: Layer2>(
67-
&self,
68-
descriptor: &WalletDescr<K, D, L2::Descr>,
69-
) -> MayError<WalletCache<L2::Cache>, Vec<Self::Error>> {
70-
let mut cache = WalletCache::new_nonsync();
71-
self.update::<K, D, L2>(descriptor, &mut cache).map(|_| cache)
72-
}
73-
7467
fn update<K, D: Descriptor<K>, L2: Layer2>(
7568
&self,
7669
descriptor: &WalletDescr<K, D, L2::Descr>,
7770
cache: &mut WalletCache<L2::Cache>,
71+
prune: bool,
7872
) -> MayError<usize, Vec<Self::Error>> {
7973
let mut errors = Vec::<ElectrumError>::new();
8074

75+
// First, we scan all addresses.
76+
// Addresses may be re-used, so known transactions doesn't help here.
77+
// We collect these transactions, which contain the most recent information, into a new
78+
// cache. We remove old transaction, since its data are now updated (for instance, if a
79+
// transaction was re-orged, it may have a different height).
80+
81+
let mut old_cache = mem::take(&mut cache.tx);
8182
let mut address_index = BTreeMap::new();
8283
for keychain in descriptor.keychains() {
8384
let mut empty_count = 0usize;
@@ -104,6 +105,7 @@ impl Indexer for Client {
104105

105106
empty_count = 0;
106107

108+
// TODO: Separate as `WalletTx::from_electrum_history` method.
107109
let mut process_history_entry =
108110
|hr: GetHistoryRes| -> Result<WalletTx, ElectrumError> {
109111
let txid = hr.tx_hash;
@@ -202,6 +204,7 @@ impl Indexer for Client {
202204
for hr in hres {
203205
match process_history_entry(hr) {
204206
Ok(tx) => {
207+
old_cache.remove(&tx.txid);
205208
cache.tx.insert(tx.txid, tx);
206209
}
207210
Err(e) => errors.push(e),
@@ -282,14 +285,22 @@ impl Indexer for Client {
282285
.insert(wallet_addr.expect_transmute());
283286
}
284287

288+
// The remaining transactions are unmined ones.
289+
if !prune {
290+
for (txid, mut tx) in old_cache {
291+
tx.status = TxStatus::Unknown;
292+
cache.tx.insert(txid, tx);
293+
}
294+
}
295+
285296
if errors.is_empty() {
286297
MayError::ok(0)
287298
} else {
288299
MayError::err(0, errors)
289300
}
290301
}
291302

292-
fn publish(&self, tx: &Tx) -> Result<(), Self::Error> {
303+
fn broadcast(&self, tx: &Tx) -> Result<(), Self::Error> {
293304
self.transaction_broadcast(tx)?;
294305
Ok(())
295306
}

src/indexers/esplora.rs

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
// limitations under the License.
2222

2323
use std::collections::BTreeMap;
24+
use std::mem;
2425
use std::num::NonZeroU32;
2526
use std::ops::{Deref, DerefMut};
2627

@@ -193,21 +194,21 @@ fn get_scripthash_txs_all(
193194
impl Indexer for Client {
194195
type Error = Error;
195196

196-
fn create<K, D: Descriptor<K>, L2: Layer2>(
197-
&self,
198-
descriptor: &WalletDescr<K, D, L2::Descr>,
199-
) -> MayError<WalletCache<L2::Cache>, Vec<Self::Error>> {
200-
let mut cache = WalletCache::new_nonsync();
201-
self.update::<K, D, L2>(descriptor, &mut cache).map(|_| cache)
202-
}
203-
204197
fn update<K, D: Descriptor<K>, L2: Layer2>(
205198
&self,
206199
descriptor: &WalletDescr<K, D, L2::Descr>,
207200
cache: &mut WalletCache<L2::Cache>,
201+
prune: bool,
208202
) -> MayError<usize, Vec<Self::Error>> {
209203
let mut errors = vec![];
210204

205+
// First, we scan all addresses.
206+
// Addresses may be re-used, so known transactions doesn't help here.
207+
// We collect these transactions, which contain the most recent information, into a new
208+
// cache. We remove old transaction, since its data are now updated (for instance, if a
209+
// transaction was re-orged, it may have a different height).
210+
211+
let mut old_cache = mem::take(&mut cache.tx);
211212
let mut address_index = BTreeMap::new();
212213
for keychain in descriptor.keychains() {
213214
let mut empty_count = 0usize;
@@ -232,7 +233,10 @@ impl Indexer for Client {
232233
}
233234
Ok(txes) => {
234235
empty_count = 0;
235-
txids = txes.iter().map(|tx| tx.txid).collect();
236+
txids.extend(txes.iter().map(|tx| tx.txid));
237+
for txid in &txids {
238+
old_cache.remove(txid);
239+
}
236240
cache
237241
.tx
238242
.extend(txes.into_iter().map(WalletTx::from).map(|tx| (tx.txid, tx)));
@@ -313,12 +317,20 @@ impl Indexer for Client {
313317
.insert(wallet_addr.expect_transmute());
314318
}
315319

320+
// The remaining transactions are unmined ones.
321+
if !prune {
322+
for (txid, mut tx) in old_cache {
323+
tx.status = TxStatus::Unknown;
324+
cache.tx.insert(txid, tx);
325+
}
326+
}
327+
316328
if errors.is_empty() {
317329
MayError::ok(0)
318330
} else {
319331
MayError::err(0, errors)
320332
}
321333
}
322334

323-
fn publish(&self, tx: &Tx) -> Result<(), Self::Error> { self.inner.broadcast(tx) }
335+
fn broadcast(&self, tx: &Tx) -> Result<(), Self::Error> { self.inner.broadcast(tx) }
324336
}

src/indexers/mod.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,21 @@ pub trait Indexer {
4545
fn create<K, D: Descriptor<K>, L2: Layer2>(
4646
&self,
4747
descr: &WalletDescr<K, D, L2::Descr>,
48-
) -> MayError<WalletCache<L2::Cache>, Vec<Self::Error>>;
48+
) -> MayError<WalletCache<L2::Cache>, Vec<Self::Error>> {
49+
let mut cache = WalletCache::new_nonsync();
50+
self.update::<K, D, L2>(descr, &mut cache, true).map(|_| cache)
51+
}
4952

53+
/// Update the wallet transaction cache and balances
54+
///
55+
/// If `prune` argument is set, removes all transactions which are not present in blockchain or
56+
/// mempool (not known to the indexer, i.e. has `TxStatus::Unknown`).
5057
fn update<K, D: Descriptor<K>, L2: Layer2>(
5158
&self,
5259
descr: &WalletDescr<K, D, L2::Descr>,
5360
cache: &mut WalletCache<L2::Cache>,
61+
prune: bool,
5462
) -> MayError<usize, Vec<Self::Error>>;
5563

56-
fn publish(&self, tx: &Tx) -> Result<(), Self::Error>;
64+
fn broadcast(&self, tx: &Tx) -> Result<(), Self::Error>;
5765
}

src/wallet.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ use psbt::{PsbtConstructor, Utxo};
3636

3737
use crate::{
3838
BlockInfo, CoinRow, Indexer, Layer2, Layer2Cache, Layer2Data, Layer2Descriptor, Layer2Empty,
39-
MayError, MiningInfo, NoLayer2, Party, TxRow, WalletAddr, WalletTx, WalletUtxo,
39+
MayError, MiningInfo, NoLayer2, Party, TxRow, TxStatus, WalletAddr, WalletTx, WalletUtxo,
4040
};
4141

4242
#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error)]
@@ -340,12 +340,16 @@ impl<L2C: Layer2Cache> WalletCache<L2C> {
340340
&mut self,
341341
descriptor: &WalletDescr<K, D, L2::Descr>,
342342
indexer: &I,
343+
prune: bool,
343344
) -> MayError<usize, Vec<I::Error>> {
344-
let res = indexer.update::<K, D, L2>(descriptor, self);
345+
let res = indexer.update::<K, D, L2>(descriptor, self, prune);
345346
self.mark_dirty();
346347
res
347348
}
348349

350+
/// Prunes transaction cache by removing all transactions with `TxStatus::Unknown`
351+
pub fn prune(&mut self) { self.tx.retain(|_, tx| tx.status != TxStatus::Unknown) }
352+
349353
pub fn addresses_on(&self, keychain: Keychain) -> &BTreeSet<WalletAddr> {
350354
self.addr.get(&keychain).unwrap_or_else(|| {
351355
panic!("keychain #{keychain} is not supported by the wallet descriptor")
@@ -558,10 +562,13 @@ impl<K, D: Descriptor<K>, L2: Layer2> Wallet<K, D, L2> {
558562
res
559563
}
560564

561-
pub fn update<I: Indexer>(&mut self, indexer: &I) -> MayError<(), Vec<I::Error>> {
562-
self.cache.update::<I, K, D, L2>(&self.descr, indexer).map(|_| ())
565+
pub fn update<I: Indexer>(&mut self, indexer: &I, prune: bool) -> MayError<(), Vec<I::Error>> {
566+
self.cache.update::<I, K, D, L2>(&self.descr, indexer, prune).map(|_| ())
563567
}
564568

569+
/// Prunes transaction cache by removing all transactions with `TxStatus::Unknown`
570+
pub fn prune(&mut self) { self.cache.prune() }
571+
565572
pub fn to_deriver(&self) -> D
566573
where
567574
D: Clone,

0 commit comments

Comments
 (0)