1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// Copyright 2021 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0
use instant::Instant;
use crate::{
client::secret::SecretManage,
wallet::{account::SyncOptions, task, Account, Wallet},
};
impl<S: 'static + SecretManage> Wallet<S>
where
crate::wallet::Error: From<S::Error>,
{
/// Find accounts with unspent outputs.
///
/// Arguments:
///
/// * `account_start_index`: The index of the first account to search for.
/// * `account_gap_limit`: The number of accounts to search for, after the last account with unspent outputs.
/// * `address_gap_limit`: The number of addresses to search for, after the last address with unspent outputs, in
/// each account.
/// * `sync_options`: Optional parameter to specify the sync options. The `address_start_index` and `force_syncing`
/// fields will be overwritten to skip existing addresses.
///
/// Returns:
///
/// A vector of Account
pub async fn recover_accounts(
&self,
account_start_index: u32,
account_gap_limit: u32,
address_gap_limit: u32,
sync_options: Option<SyncOptions>,
) -> crate::wallet::Result<Vec<Account<S>>> {
log::debug!("[recover_accounts]");
let start_time = Instant::now();
let mut max_account_index_to_keep = None;
// Search for addresses in current accounts
for account in self.accounts.read().await.iter() {
// If the gap limit is 0, there is no need to search for funds
if address_gap_limit > 0 {
account
.search_addresses_with_outputs(address_gap_limit, sync_options.clone())
.await?;
}
let account_index = *account.details().await.index();
match max_account_index_to_keep {
Some(max_account_index) => {
if account_index > max_account_index {
max_account_index_to_keep = Some(account_index);
}
}
None => max_account_index_to_keep = Some(account_index),
}
}
// Create accounts below account_start_index, because we don't want to have gaps in the accounts, but we also
// don't want to sync them
for _ in max_account_index_to_keep.unwrap_or(0)..account_start_index {
// Don't return possible errors here, because we could then still have empty accounts
let _ = self.create_account().finish().await;
}
// Don't return possible errors here already, because we would then still have empty accounts
let new_accounts_discovery_result = self
.search_new_accounts(
account_gap_limit,
address_gap_limit,
&mut max_account_index_to_keep,
sync_options.clone(),
)
.await;
// remove accounts without outputs
let mut new_accounts = Vec::new();
let mut accounts = self.accounts.write().await;
for account in accounts.iter() {
let account_index = *account.details().await.index();
let mut keep_account = false;
if let Some(max_account_index_to_keep) = max_account_index_to_keep {
if account_index <= max_account_index_to_keep {
new_accounts.push((account_index, account.clone()));
keep_account = true;
}
}
if !keep_account {
// accounts are stored during syncing, delete the empty accounts again
#[cfg(feature = "storage")]
{
log::debug!("[recover_accounts] delete empty account {}", account_index);
self.storage_manager.write().await.remove_account(account_index).await?;
}
}
}
new_accounts.sort_by_key(|(index, _acc)| *index);
*accounts = new_accounts.into_iter().map(|(_, acc)| acc).collect();
drop(accounts);
// Handle result after cleaning up the empty accounts
new_accounts_discovery_result?;
log::debug!("[recover_accounts] finished in {:?}", start_time.elapsed());
Ok(self.accounts.read().await.clone())
}
/// Generate new accounts and search for unspent outputs
async fn search_new_accounts(
&self,
account_gap_limit: u32,
address_gap_limit: u32,
max_account_index_to_keep: &mut Option<u32>,
sync_options: Option<SyncOptions>,
) -> crate::wallet::Result<()> {
let mut updated_account_gap_limit = account_gap_limit;
loop {
log::debug!("[recover_accounts] generating {updated_account_gap_limit} new accounts");
// Generate account with addresses and get their outputs in parallel
let results = futures::future::try_join_all((0..updated_account_gap_limit).map(|_| {
let mut new_account = self.create_account();
let sync_options_ = sync_options.clone();
async move {
task::spawn(async move {
let new_account = new_account.finish().await?;
let account_outputs_count = new_account
.search_addresses_with_outputs(address_gap_limit, sync_options_)
.await?;
let account_index = *new_account.details().await.index();
crate::wallet::Result::Ok((account_index, account_outputs_count))
})
.await?
}
}))
.await?;
let mut new_accounts_with_outputs = 0;
let mut highest_account_index = 0;
for (account_index, outputs_count) in results {
if outputs_count != 0 {
new_accounts_with_outputs += 1;
match *max_account_index_to_keep {
Some(max_account_index) => {
if account_index > max_account_index {
*max_account_index_to_keep = Some(account_index);
}
}
None => *max_account_index_to_keep = Some(account_index),
}
}
if account_index > highest_account_index {
highest_account_index = account_index;
}
}
// Break if there is no new account with outputs
if new_accounts_with_outputs == 0 {
break;
}
// Update account_gap_limit to only create so many new accounts, that we would check the initial provided
// account_gap_limit amount of empty accounts
if let Some(max_account_index_to_keep) = &max_account_index_to_keep {
let empty_accounts_in_row = highest_account_index - max_account_index_to_keep;
log::debug!("[recover_accounts] empty_accounts_in_row {empty_accounts_in_row}");
updated_account_gap_limit = account_gap_limit - empty_accounts_in_row;
}
}
Ok(())
}
}